diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-12-01 17:59:30 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-12-01 18:06:46 +0100 |
| commit | 11e3ee75fcab0ab54b2bc1713a402c5cc3070c2d (patch) | |
| tree | 7cb478ac6b29f2b527eb3ec379b305b74dd4f0ba /src/pages/sujet/[slug].tsx | |
| parent | dfdbf6cac1fe3719dc71e130129d28e04ba4e225 (diff) | |
refactor(pages): refine Topic pages
* add useTopic and useTopicsList hooks to refresh data
* add a table of contents
* add Cypress tests
Diffstat (limited to 'src/pages/sujet/[slug].tsx')
| -rw-r--r-- | src/pages/sujet/[slug].tsx | 202 |
1 files changed, 130 insertions, 72 deletions
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index 185756b..8a9c2f3 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -15,17 +15,24 @@ import { PageHeader, PageSidebar, PageBody, + LoadingPage, + TocWidget, + Spinner, } from '../../components'; import { convertWPTopicPreviewToPageLink, - convertWPTopicToTopic, fetchAllTopicsSlugs, fetchTopic, fetchTopicsCount, fetchTopicsList, } from '../../services/graphql'; import styles from '../../styles/pages/blog.module.scss'; -import type { NextPageWithLayout, PageLink, Topic } from '../../types'; +import type { + GraphQLConnection, + NextPageWithLayout, + WPTopic, + WPTopicPreview, +} from '../../types'; import { CONFIG } from '../../utils/config'; import { ROUTES } from '../../utils/constants'; import { @@ -34,21 +41,45 @@ import { getSchemaJson, getSinglePageSchema, getWebPageSchema, + slugify, } from '../../utils/helpers'; import { loadTranslation, type Messages } from '../../utils/helpers/server'; -import { useBreadcrumb } from '../../utils/hooks'; +import { + useBreadcrumb, + useHeadingsTree, + useTopic, + useTopicsList, +} from '../../utils/hooks'; export type TopicPageProps = { - currentTopic: Topic; - topics: PageLink[]; + data: { + currentTopic: WPTopic; + otherTopics: GraphQLConnection<WPTopicPreview>; + totalTopics: number; + }; translation: Messages; }; -const TopicPage: NextPageWithLayout<TopicPageProps> = ({ - currentTopic, - topics, -}) => { - const { content, intro, meta, slug, title } = currentTopic; +const TopicPage: NextPageWithLayout<TopicPageProps> = ({ data }) => { + const intl = useIntl(); + const { isFallback } = useRouter(); + const { isLoading, topic } = useTopic( + data.currentTopic.slug, + data.currentTopic + ); + const { isLoading: areTopicsLoading, topics } = useTopicsList({ + fallback: data.otherTopics, + input: { first: data.totalTopics, where: { notIn: [topic.id] } }, + }); + const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ + title: topic.title, + url: `${ROUTES.TOPICS}/${topic.slug}`, + }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); + + if (isFallback || isLoading) return <LoadingPage />; + + const { content, intro, meta, slug, title } = topic; const { articles, cover, @@ -57,17 +88,11 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ relatedThematics, website: officialWebsite, } = meta; - const intl = useIntl(); - const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ - title, - url: `${ROUTES.TOPICS}/${slug}`, - }); - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, locale: CONFIG.locales.defaultLocale, - slug: asPath, + slug, title: seo.title, updateDate: dates.update, }); @@ -78,30 +103,46 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ id: 'topic', kind: 'page', locale: CONFIG.locales.defaultLocale, - slug: asPath, + slug, title, }); const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]); - const topicsListTitle = intl.formatMessage({ - defaultMessage: 'Other topics', - description: 'TopicPage: other topics list widget title', - id: 'JpC3JH', - }); - - const thematicsListTitle = intl.formatMessage({ - defaultMessage: 'Related thematics', - description: 'TopicPage: related thematics list widget title', - id: '/sRqPT', - }); + const messages = { + widgets: { + loadingTopicsList: intl.formatMessage({ + defaultMessage: 'Topics are loading...', + description: 'TopicPage: loading topics message', + id: 'uUIgCr', + }), + thematicsListTitle: intl.formatMessage({ + defaultMessage: 'Related thematics', + description: 'TopicPage: related thematics list widget title', + id: '/sRqPT', + }), + tocTitle: intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }), + topicsListTitle: intl.formatMessage({ + defaultMessage: 'Other topics', + description: 'TopicPage: other topics list widget title', + id: 'JpC3JH', + }), + }, + browsePostsTitle: intl.formatMessage( + { + defaultMessage: 'Browse posts in {topicName} topic', + description: 'TopicPage: posts list heading', + id: 'd+DOFQ', + }, + { topicName: title } + ), + }; - const getPageHeading = () => ( - <> - {cover ? <NextImage {...cover} className={styles['topic-logo']} /> : null} - {title} - </> - ); - const pageUrl = `${CONFIG.url}${asPath}`; + const pageUrl = `${CONFIG.url}${slug}`; + const browsePostHeadingId = slugify(messages.browsePostsTitle); return ( <Page breadcrumbs={breadcrumbItems}> @@ -129,7 +170,14 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ type="application/ld+json" /> <PageHeader - heading={getPageHeading()} + heading={ + <> + {cover ? ( + <NextImage {...cover} className={styles['topic-logo']} /> + ) : null} + {title} + </> + } intro={intro} meta={{ publicationDate: dates.publication, @@ -138,23 +186,33 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ website: officialWebsite, }} /> - <PageBody className={styles.body}> - {/*eslint-disable-next-line react/no-danger -- Necessary for content*/} - {content ? <div dangerouslySetInnerHTML={{ __html: content }} /> : null} + <PageSidebar> + <TocWidget + heading={<Heading level={2}>{messages.widgets.tocTitle}</Heading>} + tree={[ + ...tree, + { + children: [], + depth: 2, + id: browsePostHeadingId, + label: messages.browsePostsTitle, + }, + ]} + /> + </PageSidebar> + <PageBody> + <div + className={styles.body} + // eslint-disable-next-line react/no-danger -- Necessary for content + dangerouslySetInnerHTML={{ __html: content }} + ref={ref} + /> {articles ? ( <> - <Heading level={2}> - {intl.formatMessage( - { - defaultMessage: 'All posts in {topicName}', - description: 'TopicPage: posts list heading', - id: 'zEN3fd', - }, - { topicName: title } - )} + <Heading id={browsePostHeadingId} level={2}> + {messages.browsePostsTitle} </Heading> <PostsList - className={styles.list} posts={getPostsWithUrl(articles)} headingLvl={3} sortByYear @@ -166,21 +224,25 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ {relatedThematics ? ( <LinksWidget heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> + <Heading level={2}>{messages.widgets.thematicsListTitle}</Heading> } items={getLinksItemData(relatedThematics)} /> ) : null} - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData(topics)} - /> + {areTopicsLoading ? ( + <Spinner>{messages.widgets.loadingTopicsList}</Spinner> + ) : ( + <LinksWidget + heading={ + <Heading level={2}>{messages.widgets.topicsListTitle}</Heading> + } + items={getLinksItemData( + topics.edges.map((edge) => + convertWPTopicPreviewToPageLink(edge.node) + ) + )} + /> + )} </PageSidebar> </Page> ); @@ -198,23 +260,19 @@ export const getStaticProps: GetStaticProps<TopicPageProps> = async ({ }) => { const currentTopic = await fetchTopic((params as TopicParams).slug); const totalTopics = await fetchTopicsCount(); - const allTopicsEdges = await fetchTopicsList({ + const otherTopics = await fetchTopicsList({ first: totalTopics, + where: { notIn: [currentTopic.databaseId] }, }); - const allTopics = allTopicsEdges.edges.map((edge) => - convertWPTopicPreviewToPageLink(edge.node) - ); - const topicsLinks = allTopics.filter( - (topic) => topic.url !== `${ROUTES.TOPICS}/${(params as TopicParams).slug}` - ); const translation = await loadTranslation(locale); return { props: { - currentTopic: JSON.parse( - JSON.stringify(convertWPTopicToTopic(currentTopic)) - ), - topics: JSON.parse(JSON.stringify(topicsLinks)), + data: { + currentTopic: JSON.parse(JSON.stringify(currentTopic)), + otherTopics: JSON.parse(JSON.stringify(otherTopics)), + totalTopics, + }, translation, }, }; |
